﻿namespace Silverlight.Windows
{
    using System;
    using System.Windows;

    /// <summary>
    /// Describes the placement of where a Popup appears on the screen.
    /// </summary>
    public enum PlacementMode
    {
        Absolute,
        Relative,
        Top, 
        Left, 
        Bottom, 
        Right
    }

    /// <summary>
    /// Represents a pop-up window that has content.
    /// </summary>
    public class PopupWindow
    {
        private readonly Window _window;
        FrameworkElement _placementTarget;
        PlacementMode _placement;
        double _horizontalOffset;
        double _verticalOffset;
        bool _staysOpen = true;
        Action _removeFocusHandler = () => { };

        /// <summary>
        /// Initializes a new instance of the Popup class.
        /// </summary>
        public PopupWindow()
        {
            _window = new Window
            {
                Title = "Popup",
                WindowStyle = WindowStyle.None,
                TopMost = true,
                Visibility = Visibility.Collapsed
            };
            var hwnd = Hwnd.FindHwnd(_window);
            Hwnd.RemoveFromTaskBar(hwnd);
        }

        /// <summary>
        /// Gets or sets the content of the Popup.
        /// </summary>
        public FrameworkElement Child
        {
            get { return _window.Content; }
            set { _window.Content = value; }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the Popup is visible.
        /// </summary>
        public bool IsOpen
        {
            get { return _window.Visibility == Visibility.Visible; }
            set
            {
                if (value) PlaceWindow();
                _window.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
                SetFocusHandler();
            }
        }

        /// <summary>
        /// Gets or sets a value that indicates whether the Popup closes when the control is no longer in focus.
        /// </summary>
        public bool StaysOpen
        {
            get { return _staysOpen; }
            set
            {
                _staysOpen = value;
                SetFocusHandler();
            }
        }

        /// <summary>
        /// Get or sets the horizontal distance between the target origin and the popup alignment point.
        /// </summary>
        public double HorizontalOffset
        {
            get { return _horizontalOffset; }
            set
            {
                _horizontalOffset = value;
                if (IsOpen) PlaceWindow();
            }
        }

        /// <summary>
        /// Gets or sets the vertical distance between the target origin and the popup alignment point. 
        /// </summary>
        public double VerticalOffset
        {
            get { return _verticalOffset; }
            set
            {
                _verticalOffset = value;
                if (IsOpen) PlaceWindow();
            }
        }

        /// <summary>
        /// Gets or sets the width of the popup.
        /// </summary>
        public double Width
        {
            get { return _window.Width; }
            set
            {
                _window.Width = value;
                if (IsOpen) PlaceWindow();
            }
        }

        /// <summary>
        /// Gets or sets the height of the popup.
        /// </summary>
        public double Height
        {
            get { return _window.Height; }
            set
            {
                _window.Height = value;
                if (IsOpen) PlaceWindow();
            }
        }

        /// <summary>
        /// Gets or sets the element relative to which the Popup is positioned when it opens.
        /// </summary>
        public FrameworkElement PlacementTarget
        {
            get { return _placementTarget; }
            set
            {
                _placementTarget = value;
                if (IsOpen) PlaceWindow();
            }
        }

        /// <summary>
        /// Gets or sets the orientation of the Popup when the it opens.
        /// </summary>
        public PlacementMode Placement
        {
            get { return _placement; }
            set
            {
                _placement = value;
                if (IsOpen) PlaceWindow();
            }
        }

        private void PlaceWindow()
        {
            PlaceWindowWithinDisplay(GetPlacementPosition());
        }

        private Point GetPlacementPosition()
        {
            switch (Placement)
            {
                case PlacementMode.Absolute:
                    return new Point(HorizontalOffset, VerticalOffset);
                case PlacementMode.Relative:
                    return GetRelativePosition(0, 0);
                case PlacementMode.Top:
                    return GetRelativePosition(0, -this.Height);
                case PlacementMode.Bottom:
                    return GetRelativePosition(0, PlacementTarget.ActualHeight);
                case PlacementMode.Left:
                    return GetRelativePosition(-this.Width, 0);
                case PlacementMode.Right:
                    return GetRelativePosition(PlacementTarget.ActualWidth, 0);
                default:
                    throw new InvalidOperationException();
            }
        }

        private Point GetRelativePosition(double x, double y)
        {
            var window = Window.GetWindow(PlacementTarget);

            int chromeWidth = 0;
            int chromeHeight = 0;
            var style =
                window == Application.Current.MainWindow
                ? Deployment.Current.OutOfBrowserSettings.WindowSettings.WindowStyle
                : window.WindowStyle;
            if (style == WindowStyle.SingleBorderWindow)
            {
                chromeWidth = 10;
                chromeHeight = 32;
            }

            var transform = PlacementTarget.TransformToVisual(window.Content);
            var point = transform.Transform(new Point(0, 0));

            return new Point(
                window.Left + chromeWidth + point.X + HorizontalOffset + x,
                window.Top + chromeHeight + point.Y + VerticalOffset + y
            );
        }

        private void PlaceWindowWithinDisplay(Point point)
        {
            var monitor = DisplayMonitors.GetMonitorInfoFromPoint(point);
            if (point.Y < monitor.WorkArea.Top)
                point.Y = monitor.WorkArea.Top;
            if (point.Y + this.Height > monitor.WorkArea.Bottom)
                point.Y = monitor.WorkArea.Bottom - this.Height;
            if (point.X < monitor.WorkArea.Left)
                point.X = monitor.WorkArea.Left;
            if (point.X + this.Width > monitor.WorkArea.Right)
                point.X = monitor.WorkArea.Right - this.Width;
            _window.Top = point.Y;
            _window.Left = point.X;
        }

        private void SetFocusHandler()
        {
            _removeFocusHandler();
            if (IsOpen && !_staysOpen && _placementTarget != null)
            {
                var window = Window.GetWindow(PlacementTarget);
                var content = window.Content;
                content.GotFocus += TargetWindowGotFocus;
                _removeFocusHandler = () => content.GotFocus -= TargetWindowGotFocus;
            }
            else
            {
                _removeFocusHandler = () => { };
            }
        }

        private void TargetWindowGotFocus(object sender, RoutedEventArgs e)
        {
            IsOpen = false;
        }
    }
}
